﻿using System;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.ServiceProcess;
using Kobets.Automation.Chimera.Common.Models;
using Microsoft.Win32;

namespace Kobets.Automation.Chimera.Common
{
    public static class CommonMethods
    {
        [DllImport("wininet", CharSet = CharSet.Auto)]
        static extern bool InternetGetConnectedState(ref ConnectionStatusEnum flags, int dw);

        [Flags]
        enum ConnectionStatusEnum
        {
            INTERNET_CONNECTION_MODEM = 0x1,
            INTERNET_CONNECTION_LAN = 0x2,
            INTERNET_CONNECTION_PROXY = 0x4,
            INTERNET_RAS_INSTALLED = 0x10,
            INTERNET_CONNECTION_OFFLINE = 0x20,
            INTERNET_CONNECTION_CONFIGURED = 0x40
        }

        /// <summary>
        /// Returns FQDN for the localhost
        /// </summary>
        /// <returns></returns>
        public static string GetLocalhostFqdn()
        {
            var ipProperties = IPGlobalProperties.GetIPGlobalProperties();
            return String.Format("{0}.{1}", ipProperties.HostName, ipProperties.DomainName);
        }

        /// <summary>
        /// Returns IP for localhost
        /// </summary>
        /// <returns></returns>
        public static string GetLocalIp()
        {
            return Dns.GetHostEntry(Dns.GetHostName()).AddressList.Where(ip => ip.AddressFamily == AddressFamily.InterNetwork).Select(ip => ip.ToString()).FirstOrDefault();
        }

        /// <summary>
        /// Sets recovery options for a Windows service
        /// </summary>
        /// <param name="serviceName">Name of Windows service</param>
        public static void SetRecoveryOptions(string serviceName)
        {
            if (serviceName == null) throw new ArgumentNullException("serviceName");
            int exitCode;

            using (var process = new Process())
            {
                var startInfo = process.StartInfo;
                startInfo.FileName = "sc";
                startInfo.WindowStyle = ProcessWindowStyle.Hidden;
                startInfo.Arguments = String.Format("failure \"{0}\" reset= 0 actions= restart/60000", serviceName);
                process.Start();
                process.WaitForExit();
                exitCode = process.ExitCode;
                process.Close();
            }

            if (exitCode != 0)
                throw new InvalidOperationException();
        }

        /// <summary>
        /// Sets Windows service as Desktop Interactive
        /// </summary>
        /// <param name="serviceName">Name of Windows service</param>
        public static void SetServiceInteractive(string serviceName)
        {
            if (serviceName == null) throw new ArgumentNullException("serviceName");

            using (var ckey = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Services\" + serviceName, true))
            {
                if (ckey == null) return;
                if (ckey.GetValue("Type") == null) return;
                ckey.SetValue("Type", (((int)ckey.GetValue("Type")) | 256));
            }
        }

        /// <summary>
        /// Starts Windows service
        /// </summary>
        /// <param name="serviceName">Name of Windows service</param>
        public static void StartService(string serviceName)
        {
            var service = new ServiceController(serviceName);

            try
            {
                var timeoutService = TimeSpan.FromMilliseconds(10000);

                if (service.Status == ServiceControllerStatus.Stopped)
                    service.Start();

                service.WaitForStatus(ServiceControllerStatus.Running, timeoutService);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        /// <summary>
        /// Installs a certificate
        /// </summary>
        /// <param name="certificatePath">Path to .cer file</param>
        /// <param name="certificatePass">Password for the certificate</param>
        /// <param name="storeName">Name of the security store to install the certificate in</param>
        /// <param name="storeLocation">Location of the security store</param>
        public static void InstallCertificate(string certificatePath, string certificatePass, StoreName storeName, StoreLocation storeLocation)
        {
            if (certificatePath == null) throw new ArgumentNullException("certificatePath");
            if (certificatePass == null) throw new ArgumentNullException("certificatePass");

            var cert = new X509Certificate2(certificatePath, certificatePass, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
            var store = new X509Store(storeName, storeLocation);
            store.Open(OpenFlags.ReadWrite);
            store.Add(cert);
            store.Close();
        }

        /// <summary>
        /// Reads from Windows registry
        /// </summary>
        /// <param name="location">Base location</param>
        /// <param name="baseKey">Base key</param>
        /// <param name="keyName">Key name</param>
        /// <returns></returns>
        public static string RegistryRead(RegistryKey location, string baseKey, string keyName)
        {
            object res;

            using (var sk = location.OpenSubKey(baseKey.ToUpper()))
                res = (sk == null) ? null : sk.GetValue(keyName.ToUpper());

            return (res == null) ? null : res.ToString();
        }

        /// <summary>
        /// Writes to Windows registry
        /// </summary>
        /// <param name="location">Base location</param>
        /// <param name="baseKey">Base key</param>
        /// <param name="keyName">Key name</param>
        /// <param name="value">Value to write</param>
        public static void RegistryWrite(RegistryKey location, string baseKey, string keyName, string value)
        {
            using (var sk = location.CreateSubKey(baseKey, RegistryKeyPermissionCheck.ReadWriteSubTree))
                if (sk != null) sk.SetValue(keyName.ToUpper(), value, RegistryValueKind.String);
        }

        /// <summary>
        /// Deletes from Windows registry
        /// </summary>
        /// <param name="location">Base location</param>
        /// <param name="baseKey">Base key</param>
        /// <param name="keyName">Key name</param>
        public static void RegistryDelete(RegistryKey location, string baseKey, string keyName)
        {
            using (var sk = location.CreateSubKey(baseKey))
                if (sk != null && !String.IsNullOrWhiteSpace(RegistryRead(location, baseKey, keyName)))
                    sk.DeleteValue(keyName);
        }

        /// <summary>
        /// Returns currently executing assembly name
        /// </summary>
        /// <returns></returns>
        public static string GetAssemblyName()
        {
            var assemblyName = AppDomain.CurrentDomain.FriendlyName;
            var dotPosition = assemblyName.IndexOf('.');

            if (dotPosition > 0)
                assemblyName = assemblyName.Substring(0, dotPosition);

            return assemblyName;
        }

        /// <summary>
        /// Pings host to check if it's alive
        /// </summary>
        /// <param name="host"></param>
        /// <returns></returns>
        public static bool IsHostAlive(string host)
        {
            var address = GetIpFromHost(ref host);
            var pingOptions = new PingOptions(128, true);
            var ping = new Ping();
            var buffer = new byte[32];

            if (HasConnection())
            {
                for (var i = 0; i < 4; i++)
                {
                    try
                    {
                        //send the ping 4 times to the host and record the returned data.
                        //The Send() method expects 4 items:
                        //1) The IPAddress we are pinging
                        //2) The timeout value
                        //3) A buffer (our byte array)
                        //4) PingOptions
                        var pingReply = ping.Send(address, 1000, buffer, pingOptions);
                        if (pingReply == null) continue;

                        switch (pingReply.Status)
                        {
                            case IPStatus.Success:
                                return true;
                            case IPStatus.TimedOut:
                                return false;
                            default:
                                return false;
                        }
                    }
                    catch (PingException)
                    {
                        return false;
                    }
                    catch (SocketException)
                    {
                        return false;
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Checks if there is an internet connection
        /// </summary>
        /// <returns></returns>
        private static bool HasConnection()
        {
            //instance of our ConnectionStatusEnum
            ConnectionStatusEnum state = 0;

            //call the API
            InternetGetConnectedState(ref state, 0);

            //check the status, if not offline and the returned state
            //isnt 0 then we have a connection
            return ((int)ConnectionStatusEnum.INTERNET_CONNECTION_OFFLINE & (int)state) == 0;
            //return false, no connection available
        }

        /// <summary>
        /// Returns IP for a given host
        /// </summary>
        /// <param name="host"></param>
        /// <returns></returns>
        private static IPAddress GetIpFromHost(ref string host)
        {
            IPAddress address = null;

            try
            {
                address = Dns.GetHostEntry(host).AddressList[0];
            }
            catch {}

            return address;
        }

        public static JobStatus GetJobStatus(Enums.JobStatus jobStatus)
        {
            using (var db = new ChimeraContext())
            {
                var res = db.JobStatuses.SingleOrDefault(s => s.Name == jobStatus.ToString());

                if (res == null)
                {
                    var jStatus = new JobStatus
                    {
                        Id = Guid.NewGuid(),
                        Name = jobStatus.ToString()
                    };
                    db.JobStatuses.Add(jStatus);
                    db.SaveChanges();

                    return jStatus;
                }

                return res;
            }
        }
    }
}
